/**
 * <p>Title: JAsteroids</p>
 * <p>Description: The game!!</p>
 * @author Guilherme Mauro Germoglio
 */


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class JAsteroids
    extends JFrame
    implements ActionListener, MenuListener {

  // atributes
  private CustomJPanel panel;
  private JPanel scorePanel, gamePanel;
  private Engine engine;
  private Ammo ammo;
  private AsteroidBelt asteroidBelt;
  private Spaceship spaceship;
  private JLabel scoreLabel;
  private JLabel staticScoreLabel;
  private PlayerControlEventLauncher playerControlEventLauncher;

  //methods

  /**
   * The frame constructor. Adds the menu bar, sets the size... blah.. blah...
   */
  public JAsteroids() {

    setTitle("JAsteroids 1.01");
    setSize(500, 500);
    setResizable(false);

    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setLocationRelativeTo(null);

    // adds the label where the score will be writen
    scoreLabel = new JLabel("0.0");
    staticScoreLabel = new JLabel("Score: ");
    panel = new CustomJPanel();
    gamePanel = new JPanel();
    gamePanel.setSize(500, 300);

    scorePanel = new JPanel(new FlowLayout());
    scorePanel.setBorder(BorderFactory.createEtchedBorder());
    scorePanel.add(staticScoreLabel);
    scorePanel.add(scoreLabel);

    getContentPane().add(scorePanel, BorderLayout.NORTH);
    getContentPane().add(panel, BorderLayout.CENTER);

    // the key listener
    addKeyListener(new KeyListener());
    setVisible(true);

    // creates the engine, which will make the game work
    engine = new Engine(panel);

    // creates the playerControlEventLauncher
    playerControlEventLauncher = new PlayerControlEventLauncher();

    // adds the menu bar
    JMenuBar menuBar = new JMenuBar();
    setJMenuBar(menuBar);

    // adds the "Game" menu
    JMenu gameMenu = new JMenu("Game");
    gameMenu.setMnemonic('G');
    gameMenu.addMenuListener(this);
    menuBar.add(gameMenu);

    // adds the "New game" option
    JMenuItem newGameItem = new JMenuItem("New game", 'N');
    newGameItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2,
        0));
    newGameItem.addActionListener(this);
    gameMenu.add(newGameItem);

    // adds the "High Scores" option
    JMenuItem highScoresItem = new JMenuItem("High Scores", 'S');
    highScoresItem.addActionListener(this);
    gameMenu.add(highScoresItem);

    gameMenu.addSeparator();

    // adds the "Quit" option
    JMenuItem quitItem = new JMenuItem("Quit", 'Q');
    quitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X,
        InputEvent.CTRL_MASK));
    quitItem.addActionListener(this);
    gameMenu.add(quitItem);

    // adds the "Help" menu
    JMenu helpMenu = new JMenu("Help");
    helpMenu.setMnemonic('H');
    helpMenu.addMenuListener(this);
    menuBar.add(helpMenu);

    // adds the "Help" option
    JMenuItem helpItem = new JMenuItem("Help", 'H');
    helpItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
    helpItem.addActionListener(this);
    helpMenu.add(helpItem);
    helpMenu.addSeparator();

    // adds the "About" option
    JMenuItem aboutItem = new JMenuItem("About...", 'A');
    aboutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
        InputEvent.CTRL_MASK));
    aboutItem.addActionListener(this);
    helpMenu.add(aboutItem);

    show();
  }

  /**
   * Make things work when a menu item is selected
   * @param evt The event launched when it was selected
   */
  public void actionPerformed(ActionEvent evt) {
    if (evt.getSource()instanceof JMenuItem) {
      String arg = evt.getActionCommand();
      if (arg.equals("New game")) {
        ammo = new Ammo();
        asteroidBelt = new AsteroidBelt(1, 4, 25);
        spaceship = new Spaceship(250, 250);
        panel.restart(asteroidBelt, spaceship, ammo);
        engine.newGame(panel, scoreLabel, asteroidBelt, spaceship, ammo, true);
      }
      else if (arg.equals("High Scores"))
        engine.showHighScores();
      else if (arg.equals("Quit"))
        System.exit(0);
      else if (arg.equals("Help"))
        engine.showHelp();
      else if (arg.equals("About..."))
        engine.showAbout();
    }
  }

  /**
   * It would do something if the menu is canceled, but I didn't found nothing
   * useful to do when this happens.
   * @param evt The event launched
   */
  public void menuCanceled(MenuEvent evt) {

  }

  /**
   * It if resumes the game if it was paused when the menu was selected
   * @param evt The event.
   */
  public void menuDeselected(MenuEvent evt) {
    if (engine.isRunning())
      engine.depauseGame();
    engine.menuDeselected();
  }

  /**
   * It pauses the game if it was runnning.
   * @param evt The event captured
   */
  public void menuSelected(MenuEvent evt) {
    if (engine.isRunning())
      engine.pauseGame();
    engine.menuSelected();
  }

  /**
   * <p>Title: CustomJPanel</p>
   * <p>Description: A panel which just fit my needs - listens the UniversalEvents</p>
   * @author Guilherme Mauro Germoglio
   */
  class CustomJPanel
      extends JPanel
      implements UniversalEventListener, PlayerControlEventListener {
    // atributes
    private Beholder beholder;
    private AsteroidBelt asteroidBelt;
    private Spaceship spaceship;
    private Ammo ammo;

    // methods
    /**
     * The panel's constructor. Does the samething any panel constructor does. Whatever
     * it is...
     */
    public CustomJPanel() {

    }

    /**
     * Restarts the beholder. The object that creates UniversalEvents when some
     * shit happens.
     * @param asteroidBelt The new asteroid belt
     * @param spaceship The brand new spaceship
     * @param ammo The new shell collector
     */
    public void restart(AsteroidBelt asteroidBelt, Spaceship spaceship,
                        Ammo ammo) {
      this.asteroidBelt = asteroidBelt;
      this.spaceship = spaceship;
      this.ammo = ammo;
      this.beholder = new Beholder(asteroidBelt, spaceship, ammo, this);
      beholder.addUniversalEventListener(this);
      playerControlEventLauncher.addPlayerControlEventListener(this);
    }

    /**
     * Treats the shit that happened with care.
     * @param evt The UniversalEvent that was captured when some shit happened,
     * like an asteroid was hit, the ship was destroyed... things like that.
     */
    public void shitHappens(UniversalEvent evt) {
      switch (evt.getID()) {
        case UniversalEvent.ASTEROID_HIT:
          AsteroidWasHitEvent awh = (AsteroidWasHitEvent) evt;
          engine.removeAsteroid(awh.getAsteroidIndex());
          engine.removeBullet(awh.getBulletIndex());
          engine.increaseScore();
          break;
        case UniversalEvent.SPACESHIP_HIT:
          engine.spaceshipWasHit();
          engine.gameOver();
          break;
        case UniversalEvent.GAME_OVER:
          engine.gameOver();
          break;
        case UniversalEvent.REMOVE_BULLETS:
          engine.removeBullets();
          break;
        case UniversalEvent.NEW_LEVEL:
          ammo = new Ammo();
          asteroidBelt = new AsteroidBelt(asteroidBelt.
                                          getInitialNumberOfAsteroids() + 1, 5,
                                          25);
          spaceship = new Spaceship(250, 250);
          panel.restart(asteroidBelt, spaceship, ammo);
          engine.newGame(panel, scoreLabel, asteroidBelt, spaceship, ammo, false);
          break;
        case UniversalEvent.ASTEROID_HITS_ASTEROID:
          AsteroidHitsAsteroidEvent aha = (AsteroidHitsAsteroidEvent)evt;
          engine.asteroidHitsAsteroid(aha.getAsteroid_A_Index(), aha.getAsteroid_B_Index());
          break;
      }
    }

    /**
     * Treats the player's control events with care.
     * @param evt The event correspondent to the spaceship navigation controls.
     */
    public void whatThePlayerDid(PlayerControlEvent evt) {
      switch (evt.getID()) {
        case PlayerControlEvent.SPACESHIP_FRONT_THRUSTER_START:
          engine.setSpaceshipThruster(true);
          break;
        case PlayerControlEvent.SPACESHIP_FRONT_THRUSTER_STOP:
          engine.setSpaceshipThruster(false);
          break;
        case PlayerControlEvent.SPACESHIP_REAR_THRUSTER_START:
          engine.setSpaceshipRearThruster(true);
          break;
        case PlayerControlEvent.SPACESHIP_REAR_THRUSTER_STOP:
          engine.setSpaceshipRearThruster(false);
          break;
        case PlayerControlEvent.SPACESHIP_SHOOT:
          engine.shoot();
          break;
        case PlayerControlEvent.SPACESHIP_TURN_CLOCKWISE_START:
          engine.setSpaceshipTurnClockwise(true);
          break;
        case PlayerControlEvent.SPACESHIP_TURN_CLOCKWISE_STOP:
          engine.setSpaceshipTurnClockwise(false);
          break;
        case PlayerControlEvent.SPACESHIP_TURN_COUNTER_CLOCKWISE_START:
          engine.setSpaceshipTurnCounterClockwise(true);
          break;
        case PlayerControlEvent.SPACESHIP_TURN_COUNTER_CLOCKWISE_STOP:
          engine.setSpaceshipTurnCounterClockwise(false);
          break;
      }
    }

  }

  /**
   * <p>Title: KeyListener</p>
   * <p>Description: A class that listens keyboard events, what more?</p>
   * @author Guilherme Mauro Germoglio
   */
  class KeyListener
      extends KeyAdapter {

    /**
     * Well... when a key is pressed, if it is the upper arrow, turns on the thruster
     * if it's the down arrow, turns on the rear thruster, left and right arrows make,
     * in order, the spaceship turn counter clockwise or clockwise.
     * @param e The keyEvent captured.
     */
    public void keyPressed(KeyEvent e) {
      if (engine.isRunning()) {
        switch (e.getKeyCode()) {
          case KeyEvent.VK_UP:
            playerControlEventLauncher.startSpaceshipFrontThruster();
            break;
          case KeyEvent.VK_DOWN:
            playerControlEventLauncher.startSpaceshipRearThruster();
            break;
          case KeyEvent.VK_LEFT:
            playerControlEventLauncher.startSpaceshipTurnCounterClockwise();
            break;
          case KeyEvent.VK_RIGHT:
            playerControlEventLauncher.startSpaceshipTurnClockwise();
            break;
        }
      }
    }

    /**
     * It's 1:00 a.m. I won't describe all the things this method does. Wanna know?
     * Read the code.
     * @param e The KeyEvent
     */
    public void keyReleased(KeyEvent e) {
      if (engine.isRunning()) {
        switch (e.getKeyCode()) {
          case KeyEvent.VK_UP:
            playerControlEventLauncher.stopSpaceshipFrontThruster();
            break;
          case KeyEvent.VK_DOWN:
            playerControlEventLauncher.stopSpaceshipRearThruster();
            break;
          case KeyEvent.VK_LEFT:
            playerControlEventLauncher.stopSpaceshipTurnCounterClockwise();
            break;
          case KeyEvent.VK_RIGHT:
            playerControlEventLauncher.stopSpaceshipTurnClockwise();
            break;
          case KeyEvent.VK_SPACE:
            playerControlEventLauncher.spaceshipShoot();
            break;
          case KeyEvent.VK_P:
            engine.pressedPauseButton();
            break;
        }
      }
    }
  }

  /**
   * The main method! It makes the game the game!
   * @param args Won't be used. But you know Java, it needs this.
   */
  public static void main(String[] args) {
    JFrame jasteroids = new JAsteroids();
  }
}